/*
 *  Copyright 2005 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.persistence.hibernate.forum;

import com.blandware.atleap.common.util.ConvertUtil;
import com.blandware.atleap.common.util.PartialCollection;
import com.blandware.atleap.common.util.QueryInfo;
import com.blandware.atleap.model.core.User;
import com.blandware.atleap.model.forum.Forum;
import com.blandware.atleap.model.forum.Post;
import com.blandware.atleap.model.forum.Topic;
import com.blandware.atleap.persistence.forum.PostDAO;
import com.blandware.atleap.persistence.forum.TopicDAO;
import com.blandware.atleap.persistence.hibernate.core.BaseDAOHibernate;

import java.util.*;

/**
 * <p>Hibernate implementation of TopicDAO.</p>
 * <p><a href="TopicDAOHibernate.java.html"><i>View source</i></a></p>
 *
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.8 $ $Date: 2008/06/23 13:24:54 $
 */
public class TopicDAOHibernate extends BaseDAOHibernate implements TopicDAO {

    /**
     * DAO to work with posts
     */
    protected PostDAO postDAO;

    /**
     * Sets DAO to work with posts
     *
     * @param postDAO DAO to set
     */
    public void setPostDAO(PostDAO postDAO) {
        this.postDAO = postDAO;
    }

    // ~ CRUD Methods ================================================================

    /**
     * @see com.blandware.atleap.persistence.forum.TopicDAO#createTopic(com.blandware.atleap.model.forum.Topic, com.blandware.atleap.model.forum.Forum, com.blandware.atleap.model.forum.Post)
     */
    public Long createTopic(Topic topic, Forum forum, Post firstPost) {
        forum.addTopic(topic);
        Long topicId = (Long) getHibernateTemplate().save(topic);

        firstPost.setFirst(Boolean.TRUE);
        postDAO.createPost(firstPost, topic);
        return topicId;
    }

    /**
     * @see com.blandware.atleap.persistence.forum.TopicDAO#retrieveTopic(Long)
     */
    public Topic retrieveTopic(Long topicId) {
        List args = new ArrayList();
        String hql = new StringBuffer("select topic, firstPost, title.simpleValue, body.value from Topic topic ")
                .append("left outer join topic.posts as firstPost ")
                .append("left outer join firstPost.contentFields as titleField ")
                .append("left outer join titleField.contentFieldValues as title ")
                .append("left outer join firstPost.contentFields as bodyField ")
                .append("left outer join bodyField.contentFieldValues as body ")
                .append("where titleField.identifier = 'title' ")
                .append("and bodyField.identifier = 'body' ")
                .append("and title.contentLocale.identifier = topic.forum.language.identifier ")
                .append("and body.contentLocale.identifier = topic.forum.language.identifier ")
                .append("and firstPost.first = 'T' ")
                .append("and topic.id = ? ")
                .toString();
        args.add(topicId);
        Object[] objects = (Object[]) findUniqueResult(hql, args.toArray());

        Topic topic = null;

        if (objects != null) {
            Map title = new HashMap();
            topic = (Topic) objects[0];
            Post firstPost = (Post) objects[1];
            title.put(topic.getForum().getLanguage().getIdentifier(), objects[2]);
            firstPost.setTitle(title);
            firstPost.setBody(ConvertUtil.convertToString((byte[]) objects[3]));

            topic.setNumberOfReplies(getNumberOfReplies(topic.getId()));
            topic.setLastReply(getLastReply(topic.getId()));
            topic.setUsersSubscribed(getUsersSubscribedMap(topic.getId()));

            topic.setFirstPost(firstPost);
        }

        return topic;
    }

    /**
     * @see com.blandware.atleap.persistence.forum.TopicDAO#deleteTopic(com.blandware.atleap.model.forum.Topic)
     */
    public void deleteTopic(Topic topic) {
        for (Iterator i = topic.getPosts().iterator(); i.hasNext(); ) {
            Post post = (Post) i.next();
            postDAO.removeAssociations(post);
        }
        getHibernateTemplate().delete(topic);
        topic.getForum().removeTopic(topic);
    }

    // ~ Additional methods ================================================================

    /**
     * @see com.blandware.atleap.persistence.forum.TopicDAO#listTopics(com.blandware.atleap.common.util.QueryInfo)
     */
    public PartialCollection listTopics(QueryInfo queryInfo) {
		String whereClause = new String();
		String orderByClause = new String();
		if ( queryInfo != null ) {
			whereClause = queryInfo.getWhereClause();
			orderByClause = queryInfo.getOrderByClause();
		}

		if ( whereClause == null || whereClause.length() == 0 ) {
			whereClause = new String();
		}
		if ( orderByClause != null && orderByClause.length() != 0 ) {
			orderByClause = " order by " + orderByClause;
		} else {
			orderByClause = " order by firstPost.date desc ";
		}

        Long forumId = null;
        if ( queryInfo != null ) {
            if ( queryInfo.getQueryParameters() != null ) {
                forumId = (Long) queryInfo.getQueryParameters().get("forumId");
            }
        }

		List list = null;
		Integer total = null;

		String hqlPart = new String();
		ArrayList args = new ArrayList();
        hqlPart = new StringBuffer("from Topic as topic ")
                .append("left outer join topic.posts as firstPost ")
                .append("left outer join firstPost.contentFields as titleField ")
                .append("left outer join titleField.contentFieldValues as title ")
                .append("left outer join firstPost.contentFields as bodyField ")
                .append("left outer join bodyField.contentFieldValues as body ")
                .append("where titleField.identifier = 'title' ")
                .append("and bodyField.identifier = 'body' ")
                .append("and title.contentLocale.identifier = topic.forum.language.identifier ")
                .append("and body.contentLocale.identifier = topic.forum.language.identifier ")
                .append("and firstPost.first = 'T' ")
                .toString();
        hqlPart += "and topic.forum.id = ? ";
        args.add(forumId);
        if (whereClause.length() > 0) {
            hqlPart = hqlPart + "and " + whereClause;
        }

		if ( queryInfo != null && queryInfo.getLimit() != null && queryInfo.getOffset() != null ) {
			// query count
			String hqlForTotal = "select count(distinct topic.id) " + hqlPart;
			total = findUniqueIntegerResult(hqlForTotal, args.toArray());
			if ( total == null ) {
				total = new Integer(0);
			}
		}

        // If we don't have any info about the total number of results yet or
        // we know that there's something that will be found, then fetch data
		if ( total == null || total.intValue() > 0 ) {
			String hql = new String();
    		hql = "select topic, firstPost, title.simpleValue, body.value " + hqlPart + orderByClause;
			list = executeFind(hql, queryInfo, args.toArray());
            Set ids = new HashSet();
            List topics = new ArrayList();
            if (list != null) {
                for ( ListIterator i = list.listIterator(); i.hasNext(); ) {
                    Object[] objects = (Object[]) i.next();
                    Map title = new HashMap();
                    Topic topic = (Topic) objects[0];
                    Long topicId = topic.getId();
                    if (!ids.contains(topicId)) {
                        Post firstPost = (Post) objects[1];
                        title.put(topic.getForum().getLanguage().getIdentifier(), objects[2]);
                        firstPost.setTitle(title);
                        firstPost.setBody(ConvertUtil.convertToString((byte[]) objects[3]));

                        topic.setNumberOfReplies(getNumberOfReplies(topic.getId()));
                        topic.setLastReply(getLastReply(topic.getId()));
                        topic.setFirstPost(firstPost);
                        topic.setUsersSubscribed(getUsersSubscribedMap(topic.getId()));

                        topics.add(topic);
                        ids.add(topicId);
                    }
                }
            }
            list = topics;
            if ( total == null ) {
                total = new Integer(list.size());
            }
        } else {
			list = new ArrayList();
		}

		return new PartialCollection(list, total);
    }

    /**
     * Returns number of replies in a topic with a given ID (reply is a post
     * which is not first in its topic).
     *
     * @param topicId ID of topic
     * @return number of replies in a topic
     */
    protected Integer getNumberOfReplies(Long topicId) {
        String hql = new StringBuffer("select count(post) from Post as post ")
                .append("where post.topic.id = ? ")
                .append("and post.first != 'T' ")
                .toString();
        return findUniqueIntegerResult(hql, new Object[]{topicId});
    }

    /**
     * Returns last reply in a topic with a given ID (reply is a post
     * which is not first in its topic). If there're no replies in this topic,
     * <code>null</code> is returned.
     *
     * @param topicId ID of topic
     * @return last reply in a topic
     */
    protected Post getLastReply(Long topicId) {
        String hql = new StringBuffer("select post.id from Post as post ")
                .append("where post.topic.id = ? ")
                .append("and post.first != 'T' ")
                .append("order by post.date desc ")
                .toString();
        QueryInfo queryInfo = new QueryInfo();
        queryInfo.setOffset(new Integer(0));
        queryInfo.setLimit(new Integer(1));
        Long postId = null;
        List ids = executeFind(hql, queryInfo, new Object[]{topicId});
        if (ids != null && ids.size() > 0) {
            postId = (Long) ids.get(0);
        }
        Post post = null;
        if (postId != null) {
            post = postDAO.retrievePost(postId);
        }
        return post;
    }

    /**
     * Returns a map which maps username to <code>Boolean.TRUE</code> if and
     * only if that user is subscribed to this topic.
     *
     * @param topicId topic ID
     * @return map
     */
    protected Map getUsersSubscribedMap(Long topicId) {
        String hql = new StringBuffer("select fs.user from ForumSubscription as fs ")
                .append("where fs.topic.id = ? ")
                .append("and fs.user is not null ")
                .toString();
        List list = executeFind(hql, new Object[]{topicId});
        Map result = new HashMap();
        for (ListIterator i = list.listIterator(); i.hasNext(); ) {
            User user = (User) i.next();
            result.put(user.getName(), Boolean.TRUE);
        }
        return result;
    }
}
